
/**
 * Creates a custom element with the specified name and options.
 *
 * @param {string} name - The name of the custom element.
 * @param {Object} options - The options for the custom element.
 * @param {string} options.template - The HTML template for the custom element.
 * @param {string} options.style - The CSS styles for the custom element.
 * @returns {HTMLElement} The created custom element.
 */
export function createCustomElement(name, options) {
  const customElement = document.createElement(name);
  const shadowRoot = customElement.attachShadow({ mode: "open" });
  const template = document.createElement("template");
  template.innerHTML = options.template;
  const style = document.createElement("style");
  style.textContent = options.style;
  shadowRoot.appendChild(style);
  shadowRoot.appendChild(template.content.cloneNode(true));
  // 如果传入了slot属性，且slot属性是数组
  if (options.slot && Array.isArray(options.slot)) {
    options.slot.forEach(slot => {
      const slotElement = document.createElement(slot.name);
      slotElement.setAttribute("name", slot.name);
      slotElement.innerHTML = slot.template;
      shadowRoot.appendChild(slotElement);
    });
  }

  return customElement;
}

// 创建元素,并设置属性
export function createElement(name, options) {
  const element = document.createElement(name); // 创建元素
  // 设置属性,将所有属性都进行设置
  Object.keys(options).forEach(key => {
    const value = options[key];
      element.setAttribute(key, value);
  });




  

  return element;
}


// 插槽
export function createSlot(name, options) {
  const slot = document.createElement(name);
  const shadowRoot = slot.attachShadow({ mode: "open" });
  const template = document.createElement("template");
  template.innerHTML = options.template;
  const style = document.createElement("style");
  style.textContent = options.style;
  shadowRoot.appendChild(style);
  shadowRoot.appendChild(template.content.cloneNode(true));

  return slot;
}


/**
 * 获取元素的样式属性值
 * @param {HTMLElement} el - 目标元素
 * @param {string} prop - 样式属性名
 * @returns {string} - 样式属性值
 */
export function getStyle(el, prop) {
  return window.getComputedStyle(el)[prop];
}


/**
 * Represents a utility class for manipulating styles of an element.
 */
class Style {
  constructor(el) {
    this.el = el;
  }

  /**
   * Retrieves the computed value of a specified CSS property for the element.
   * @param {string} prop - The CSS property to retrieve.
   * @returns {string} The computed value of the CSS property.
   */
  get(prop) {
    return window.getComputedStyle(this.el)[prop];
  }

  /**
   * Sets the value of a specified CSS property for the element.
   * @param {string} prop - The CSS property to set.
   * @param {string} value - The value to set for the CSS property.
   */
  set(prop, value) {
    this.el.style[prop] = value;
  }

  /**
   * Removes the value of a specified CSS property for the element.
   * @param {string} prop - The CSS property to remove.
   */
  remove(prop) {
    this.el.style[prop] = "";
  }

  /**
   * Checks if the element has a specified CSS property.
   * @param {string} prop - The CSS property to check.
   * @returns {boolean} True if the element has the CSS property, false otherwise.
   */
  has(prop) {
    return this.get(prop) !== "";
  }

  /**
   * Toggles the value of a specified CSS property for the element.
   * If the property is present, it will be removed. Otherwise, it will be set to the specified value.
   * @param {string} prop - The CSS property to toggle.
   * @param {string} value - The value to set for the CSS property if it is not present.
   */
  toggle(prop, value) {
    if (this.has(prop)) {
      this.remove(prop);
    } else {
      this.set(prop, value);
    }
  }

  // 设置 style 属性,拼接 style 字符串
  pushStyle(styleObj) {
    const style = this.el.getAttribute('style') || '';
    const styleStr = Object.keys(styleObj).reduce((styleStr, key) => {
      const value = styleObj[key];
      if (value) {
        styleStr += `${key}: ${value};`;
      }
      return styleStr;
    }, style);
    this.el.setAttribute('style', styleStr);
  }
  
}

/**
 * Represents an attribute of an element.
 */
class Attribute {
  constructor(el) {
    this.el = el;
  }

  /**
   * Get the value of the specified attribute.
   * @param {string} attr - The name of the attribute.
   * @returns {string|null} - The value of the attribute, or null if the attribute does not exist.
   */
  get(attr) {
    return this.el.getAttribute(attr);
  }

  /**
   * Set the value of the specified attribute.
   * @param {string} attr - The name of the attribute.
   * @param {string} value - The value to set for the attribute.
   */
  set(attr, value) {
    this.el.setAttribute(attr, value);
  }

  /**
   * Remove the specified attribute from the element.
   * @param {string} attr - The name of the attribute to remove.
   */
  remove(attr) {
    this.el.removeAttribute(attr);
  }

  /**
   * Check if the element has the specified attribute.
   * @param {string} attr - The name of the attribute to check.
   * @returns {boolean} - True if the element has the attribute, false otherwise.
   */
  has(attr) {
    return this.el.hasAttribute(attr);
  }

  /**
   * Toggle the specified attribute on the element.
   * If the attribute exists, it will be removed. If it does not exist, it will be added.
   * @param {string} attr - The name of the attribute to toggle.
   */
  toggle(attr) {
    if (this.has(attr)) {
      this.remove(attr);
    } else {
      this.set(attr, value);
    }
  }
}

/**
 * Represents a class manipulation utility for DOM elements.
 */
class Class {
  constructor(el) {
    this.el = el;
  }

  /**
   * Gets the class list of the DOM element.
   * @returns 
   */
  get() {
    return this.el.classList;
  }

  /**
   *  Sets the class list of the DOM element.
   * @param {*} className 
   */
  set(className) {
    this.el.classList.add(className);
  }

  /**
   *  Removes the class list of the DOM element.
   * @param {*} className 
   */
  remove(className) {
    this.el.classList.remove(className);
  }

  /**
   *  Checks if the DOM element has the specified class.
   * @param {*} className 
   * @returns 
   */
  has(className) {
    return this.el.classList.contains(className);
  }

  /**
   * Toggles the class list of the DOM element.
   * @param {*} className 
   */
  toggle(className) {
    if (this.has(className)) {
      this.remove(className);
    } else {
      this.set(className);
    }
  }
}


/**
 * Represents an event handler for a DOM element.
 */
class Event {
  constructor(el) {
    this.el = el;
  }

  /**
   * Adds an event listener to the DOM element.
   * @param {string} event - The event name.
   * @param {Function} handler - The event handler function.
   */
  on(event, handler) {
    this.el.addEventListener(event, handler);
  }

  /**
   * Removes an event listener from the DOM element.
   * @param {string} event - The event name.
   * @param {Function} handler - The event handler function.
   */
  off(event, handler) {
    this.el.removeEventListener(event, handler);
  }

  /**
   * Adds an event listener to the DOM element that will only be triggered once.
   * @param {string} event - The event name.
   * @param {Function} handler - The event handler function.
   */
  once(event, handler) {
    this.el.addEventListener(event, handler, { once: true });
  }
}

/**
 * Represents a query object for DOM manipulation.
 */
class Query {
  constructor(el) {
    this.el = el;
  }

  /**
   * Selects the first element that matches the specified selector within the current element.
   * @param {string} selector - The CSS selector to match against.
   * @returns {Element|null} - The first matching element, or null if no matches found.
   */
  select(selector) {
    return this.el.querySelector(selector);
  }

  /**
   * Selects all elements that match the specified selector within the current element.
   * @param {string} selector - The CSS selector to match against.
   * @returns {NodeList} - A static (non-live) NodeList containing all matching elements.
   */
  selectAll(selector) {
    return this.el.querySelectorAll(selector);
  }

  /**
   * Selects the first element that matches the specified selector within the shadow root of the current element.
   * @param {string} selector - The CSS selector to match against.
   * @returns {Element|null} - The first matching element within the shadow root, or null if no matches found.
   */
  selectShadow(selector) {
    return this.el.shadowRoot.querySelector(selector);
  }

  /**
   * Selects all elements that match the specified selector within the shadow root of the current element.
   * @param {string} selector - The CSS selector to match against.
   * @returns {NodeList} - A static (non-live) NodeList containing all matching elements within the shadow root.
   */
  selectAllShadow(selector) {
    return this.el.shadowRoot.querySelectorAll(selector);
  }

  /**
   * Selects the closest ancestor element that matches the specified selector, starting from the current element and traversing upwards.
   * @param {string} selector - The CSS selector to match against.
   * @returns {Element|null} - The closest ancestor element that matches the selector, or null if no matches found.
   */
  selectParent(selector) {
    return this.el.closest(selector);
  }

  /**
   * Selects the closest ancestor element within the shadow root that matches the specified selector, starting from the current element and traversing upwards.
   * @param {string} selector - The CSS selector to match against.
   * @returns {Element|null} - The closest ancestor element within the shadow root that matches the selector, or null if no matches found.
   */
  selectParentShadow(selector) {
    return this.el.closest(selector);
  }
}


export default class UtilsDom {
  /**
   * Creates a new instance of UtilsDom.
   * @param {HTMLElement} el - The DOM element to manipulate.
   */
  constructor(el) {
    this.el = el;
    this.style = new Style(el);
    this.attr = new Attribute(el);
    this.class = new Class(el);
    this.event = new Event(el);
    this.query = new Query(el);
  }
}

